/**  
  -- The Performance Driven Lime Project -- 

  A self adaptive tuple space for mobile agents and hosts by dynamic
  adapting tuple's replication policy.
  
  Copyright (C) 2008
  Xing Jiankuan, Pan Wenhui
  SAGroup of Tsinghua University

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */
package sagroup.dracots;


import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
//import java.util.logging.Logger;
import java.util.Set;


import lime2.Location;

//import sagroup.dracots.CostManager.MP;
import sagroup.dracots.policy.PolicyRegistrar;
import sagroup.dracots.policy.ReplicationPolicy;
import sagroup.dracots.TupleType;
//import org.apache.commons.math.stat.regression.SimpleRegression;

/**
 * Maintain all rtup records for all types. Calculate MRtup value for certain
 * type.
 * 
 * @author Xing Jiankuan
 * 
 */
public class RTUPManager {
	
	private class MRTUP {
		Period p;
		Double average;
		Double mse;

		MRTUP(Period p, Double average, Double mse) {
			this.p = p;
			this.average = average;
			this.mse = mse;
		}
        public String toString()
        {
        	return "Period-" + p.getID() + ",Average-" + average
        			+ ",MSE-" + mse + ".";
        }
	}

	private Hashtable<TupleType, HashMap<Period, RTUP>> rtupsTable;

//	private Hashtable<TupleType, LinkedList<MRTUP>> mrtupsList;
	private Hashtable<TupleType, MRTUP> mrtupsList;
	private Hashtable<TupleType,Integer> totalPeriods;
	private Hashtable<TupleType, LinkedList<Period>> typeRtups;
	private int D; // the number of periods which is used to calculate the
//	private int F;

//	private Logger LOG = Logger.getLogger(this.getClass().getName());
		
	// mobile average
	/**
	 * 
	 */
	public RTUPManager(int D, int F, LocalReplicaRegistrar lrr,
			PolicyRegistrar r) {
		rtupsTable = new Hashtable<TupleType, HashMap<Period, RTUP>>();
//		mrtupsList = new Hashtable<TupleType, LinkedList<MRTUP>>();
		mrtupsList = new Hashtable<TupleType, MRTUP>();
		totalPeriods = new Hashtable<TupleType, Integer>();
		this.lrr = lrr;
		this.policyRegistrar = r;
		typeRtups = new Hashtable<TupleType, LinkedList<Period>>();
		this.D = D;
//		this.F = F;
	}

	
	
	/**
	 * Invoked when a unused replica is touched. Increase rtu counts.
	 * 
	 * @param t
	 * @param p
	 * @author Xing Jiankuan
	 */
	public synchronized void handleTouch(ShareTuple t, Period p) {
		if (p == null)
			return;
		TupleType type = t.getType();
		this.intializeForType(type, p);
		this.rtupsTable.get(type).get(p).addRtu(1);
		
		// The info for RTU
//		LOG.info("*****RTU->Type: " + type + " | Period: " + p    
//				+ this.rtupsTable.get(type).get(p).toString());
	}

	/**
	 * 
	 * @param type
	 * @return MRtup value of certain tuple type
	 * @author Xing Jiankuan
	 */
	public synchronized double calMRtupValue(TupleType type, Period currp) {
		double mrtup = 0;
		double mse = 0;
		HashMap<Period, RTUP> rtupMap = this.rtupsTable.get(type);
		if(rtupMap == null)
			return mrtup;
		RTUP currRtup = rtupMap.get(currp);

		if (currRtup == null){
//			System.out.println("***********Now the Rtup is null!!");
//			System.out.println("***********The whole RTUs:" + rtupMap.toString());
			return mrtup;
		}
					
		if (rtupMap != null) {
			// System.out.println(rutpMap);
			if (rtupMap.size() >= D) {
				for (RTUP rtup : rtupMap.values()) {
					mrtup += rtup.getValue();
				}
//				System.out.println("***********The whole RTUs:" + rtupMap.toString());
				mrtup = mrtup / D;
				
				for (RTUP rtup : rtupMap.values()) {
					mse += Math.pow(rtup.getValue() - mrtup, 2);
				}
//				System.out.println("***********The whole RTUs:" + rtupMap.toString());
				mse = Math.sqrt(mse / D);
			}
		}
		//
		insertMRtup(type, currp, mrtup, mse);

		return mrtup;
	}



	private void insertMRtup(TupleType type, Period p, Double mrtup, Double mse) {
		
		MRTUP m = new MRTUP(p, mrtup, mse);
		
		if (mrtupsList.containsKey(type)) {
			mrtupsList.remove(type);
		}
		
		mrtupsList.put(type, m);
		
//		if (!mrtupsList.containsKey(type)) {
//			mrtupsList.put(type, new LinkedList<MRTUP>());
//		}
//
//		MRTUP mrtup = mrtupsList.get(type);
//
//		if (list.size() >= F)// remove head if queue is full
//
//			list.removeFirst();
//
//		MRTUP m = new MRTUP(p, mrtup);
//		list.add(m);
		increasePeriod(type);
	}
	private void increasePeriod(TupleType type) {
		if(!totalPeriods.containsKey(type))
			totalPeriods.put(type, 1);
		else
			totalPeriods.put(type, totalPeriods.get(type)+1);
		
	}
	private PolicyRegistrar policyRegistrar;
	private Location here = Location.HERE;
	private LocalReplicaRegistrar lrr;

	private synchronized void intializeForType(TupleType type, Period p) {
		if (p == null)
			return;
		if (!rtupsTable.containsKey(type)) {
			rtupsTable.put(type, new HashMap<Period, RTUP>());
		}
		HashMap<Period, RTUP> rtupMap = rtupsTable.get(type);
		if (!rtupMap.containsKey(p)) {
			RTUP rtup = new RTUP();
			// put the new rtup record into list queue

			if (!typeRtups.containsKey(type)) {
				typeRtups.put(type, new LinkedList<Period>());
			}
			LinkedList<Period> rtupLink = typeRtups.get(type);
			if (rtupLink.size() >= D)// remove head if queue is full
			{
				Period removeP = rtupLink.remove();
				rtupMap.remove(removeP);
			}
			rtupLink.add(p);
			rtupMap.put(p, rtup);
		}
	}

	public synchronized void initialize(Period p) {
		if (p == null)
			return;
		for (TupleType type : rtupsTable.keySet()) {
			ReplicationPolicy policy = policyRegistrar.getReplicaPolicy(type,
					here);
			if (policy.equals(ReplicationPolicy.RC)
					|| policy.equals(ReplicationPolicy.FR)) {

				this.intializeForType(type, p);
			}
		}
	}

	public synchronized void removeRecord(TupleType type, Period currentPeriod) {

		if (currentPeriod == null)
			return;
		HashMap<Period, RTUP> map = rtupsTable.get(type);
		if (map.containsKey(currentPeriod)) {
			map.remove(currentPeriod);
		}
	}

	public boolean needToRC(TupleType type) {
		System.out.println(">>>>>>>"+totalPeriods.get(type));
		int periods = (totalPeriods.get(type)!=null)?
				totalPeriods.get(type) : 0;
		if(periods < D + 1)
		{
			return false;
		}
		boolean tag = false;
		MRTUP mrtup = this.mrtupsList.get(type);
		
		if(mrtup != null 
				&& mrtup.average < DracoConstants.THRES_RC 
				&& mrtup.mse <= DracoConstants.THRES_MSE){
			tag = true;
		}

		
//		double k = 0;
//		LinkedList<MRTUP> mList = this.mrtupsList.get(type);
//		if (mList!= null && mList.size() >= F) {
//
//			for (MRTUP m : mList) {
//				if (m.value > DracoConstants.THRES_RC) {
//					tag = false;
//					break;
//				}
//			}
//			if(tag)
//			{
//				k =  getSimpleRegression(mList);
//				if( k > -0.01)
//					tag = false;
//			}
//		} else
//			tag = false;
//		
//		if(mList != null)
		if(mrtup != null)
			{
			System.out.println(type+" Need to RC? MRTUPs: {"+ mrtup +
					"} tag: "+tag);
//			ks.put(type,k);
			}else
			{
				System.out.println("MRTUP is null , type : " + type);
			}
		return tag;
	}

//	private double getSimpleRegression(LinkedList<MRTUP> list) {
//
//		SimpleRegression regression = new SimpleRegression();
//		for (int i = 0; i < list.size(); i++) {
//			regression.addData(i, list.get(i).value);
//		}
//
//		return regression.getSlope();
//	}

	public boolean needToFR(TupleType type) {
		System.out.println(">>>>>>>"+totalPeriods.get(type));
		int periods = (totalPeriods.get(type)!=null)?
				totalPeriods.get(type) : 0;
		if(periods < D + 1)
		{
			return false;
		}
		
		boolean tag = false;
		MRTUP mrtup = this.mrtupsList.get(type);
		
		if(mrtup != null 
				&& mrtup.average > DracoConstants.THRES_FR
				&& mrtup.mse <= DracoConstants.THRES_MSE){
			tag = true;
		}

//		boolean tag = true;
//		double k = 0;
//		LinkedList<MRTUP> mList = this.mrtupsList.get(type);
//		if (mList!= null && mList.size() >= F) {
//
//			for (MRTUP m : mList) {
//				if (m.value < DracoConstants.THRES_FR) {
//					tag = false;
//					break;
//				}
//			}
//			if(tag)
//			{
//				k =  getSimpleRegression(mList);
//				if( k < -0.01)
//					tag = false;
//			}
//		} else
//			tag = false;
//		if(mList != null)
		if(mrtup != null)
		{
		System.out.println(type+" Need to FR? MRTUPs: {"+ mrtup +
				"} tag: " + tag);
//		ks.put(type,k);
		}else
		{
			System.out.println("MRUTP is null , type : " + type);
		}
		return tag;
	}
//	private HashMap<TupleType,Double> ks = new HashMap<TupleType, Double>();
//	private String getList(LinkedList<MRTUP> list) {
//		
//		
//		StringBuffer sb = new StringBuffer();
//		for(int i=0;i<list.size();i++)
//			sb.append(list.get(i)+" ");
//		return sb.toString();
//	}

	public Set<TupleType> getTypeSet() {
	
		return rtupsTable.keySet();
	}



	public void SetUnusedTuples(TupleType type, Period currentPeriod,
			long esitimatedNum,long addedCountByType, long deletedCountByType) {
		if(currentPeriod !=null && type!= null)
		{
			RTUP last = getLastRecord(type,currentPeriod);
			intializeForType(type, currentPeriod);
			rtupsTable.get(type).get(currentPeriod)
			.setUnused(last,esitimatedNum,addedCountByType,
					deletedCountByType);	
		}
	}



	private RTUP getLastRecord(TupleType type, Period currentPeriod) {
		RTUP ret = null;
		LinkedList<Period> ps =  typeRtups.get(type);
		if(ps !=null && !ps.isEmpty())
		{
			int index = ps.indexOf(currentPeriod);
			if(index >= 1 )
			{
				Period pkey = ps.get(index - 1);
				ret = this.rtupsTable.get(type).get(pkey);
			}
		}
			
		return ret;
	}



	public long getUsedCountByType(TupleType t, Period currentPeriod) {
		long count = -1;
		try
		{
			 count = this.rtupsTable.get(t).get(currentPeriod).getUsed();
		}
		catch(Exception ex)
		{
			
		}
		return count;
	}



	public double getRtupValue(TupleType t, Period currentPeriod) {
		double ret = 0;
		if(currentPeriod!=null && this.rtupsTable.containsKey(t))
	    {
		  RTUP r = rtupsTable.get(t).get(currentPeriod);
		   if(r!=null)
			   ret = r.getValue();
	    }
		return ret;
	}

	public double getMSE(TupleType type){
		Double mse = null;
		if(mrtupsList != null && type != null)
		{
			MRTUP mp;
			if((mp = this.mrtupsList.get(type)) != null)
				mse = mp.mse;
			else
				System.out.println("@@@@@@@@@@@@@the MRTUP is null for type: " + type);
		} 
		return (mse != null)? mse.doubleValue(): 1000;
	}
	
//	public double getK(TupleType type) {
//		Double k = null;
//		if(type!=null)
//		{
//		k = this.ks.get(type);
//		} 
//		return (k!=null)? k.doubleValue(): -1.0;
//	}

	
}
